package jmine.tec.persist.schema.impl.elements;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;

import jmine.tec.persist.annotation.DefaultValue;
import jmine.tec.persist.schema.api.AbstractProcessingSchemaElement;
import jmine.tec.persist.schema.api.ProcessingSchemaElement;
import jmine.tec.persist.schema.api.SchemaColumn;
import jmine.tec.persist.schema.api.SqlDialect;
import jmine.tec.persist.schema.impl.elements.type.AllowedValue;

import org.hibernate.mapping.PersistentClass;

/**
 * Elemento que representa uma coluna de uma tabela.
 * 
 * @author lundberg
 */
public class ColumnElement extends AbstractProcessingSchemaElement<ColumnProcessingBean> implements Comparable<ColumnElement>, SchemaColumn {

    private final String name;

    private boolean nullable;

    private String defaultValue;

    private ColumnTypeElement type;

    private TableElement table;

    private GenerationType generationStrategy = null;

    /**
     * Construtor
     * 
     * @param parent mensagens
     * @param name nome da coluna
     */
    public ColumnElement(String name, ProcessingSchemaElement<?> parent) {
        super(parent);
        this.name = name;
    }

    /**
     * {@inheritDoc}
     */
    public List<String> createStatements(SqlDialect dialect) {
        String defaultValueLiteral = dialect.renderLiteral(this.type.getCode(), this.defaultValue);
        return Collections.singletonList(dialect.renderColumn(this.name, this.type, this.nullable, defaultValueLiteral));
    }

    /**
     * Processa os dados da coluna
     * 
     * @param metadata dados necessários para o processamento da coluna.
     */
    public void process(ColumnProcessingBean metadata) {
        this.type.process(metadata);
        Method getter = metadata.getGetter();
        if (getter.isAnnotationPresent(GeneratedValue.class)) {
            this.generationStrategy = getter.getAnnotation(GeneratedValue.class).strategy();
        }
        if (getter.isAnnotationPresent(DefaultValue.class)) {
            this.defaultValue = getter.getAnnotation(DefaultValue.class).value();
        }
    }

    /**
     * Processa os dados da coluna, que é um identificador herdado de outra tabela.
     * 
     * @param metadata dados necessários para o processamento da coluna.
     */
    public void processInheritedIdentifier(ColumnProcessingBean metadata) {
        this.type.process(metadata);
    }

    /**
     * Efetua o processamento necessário para colunas que não se referenciadas por propriedades, mas por discriminators.
     * 
     * @param metadata mapeamento da classe persistente.
     */
    public void processDiscriminator(PersistentClass metadata) {
        this.type.processDiscriminator(metadata);
    }

    /**
     * @return nome da coluna
     */
    public String getName() {
        return this.name;
    }

    /**
     * Define o tipo de coluna
     * 
     * @param type elemento representando o tipo da coluna.
     */
    public void setType(ColumnTypeElement type) {
        type.setColumn(this);
        this.type = type;
    }

    /**
     * Define se a coluna deve ou não permitir valores nulos
     * 
     * @param nullable boolean
     */
    public void setNullable(boolean nullable) {
        this.nullable = nullable;
    }

    /**
     * Define o valor padrão da coluna.
     * 
     * @param defaultValue valor padrão
     */
    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }

    /**
     * Obtém a tabela que contém a coluna.
     * 
     * @return tabela
     */
    public TableElement getTable() {
        return this.table;
    }

    /**
     * Define a tabela que contém a coluna
     * 
     * @param table tabela
     */
    public void setTable(TableElement table) {
        this.table = table;
    }

    /**
     * {@inheritDoc}
     */
    public int compareTo(ColumnElement o) {
        return this.name.compareTo(o.name);
    }

    /**
     * Cria a coleção de check constraints que se aplicam a essa coluna.
     * 
     * @return Collection de CheckConstraintElement
     * @param tableElement Tabela
     */
    public Set<CheckConstraintElement> createCheckConstraints(TableElement tableElement) {
        return this.type.createCheckConstraints(tableElement);
    }

    /**
     * Retorna o elemento que define o tipo da coluna
     * 
     * @return column type element
     */
    public ColumnTypeElement getType() {
        return this.type;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return String.format("%s.%s", this.table == null ? "NULL" : this.table.getName(), this.name);
    }

    /**
     * {@inheritDoc}
     */
    public int getCode() {
        return this.type.getCode();
    }

    /**
     * {@inheritDoc}
     */
    public GenerationType getGenerationStrategy() {
        return this.generationStrategy;
    }

    /**
     * {@inheritDoc}
     */
    public int getLength() {
        return this.type.getLength();
    }

    /**
     * {@inheritDoc}
     */
    public int getPrecision() {
        return this.type.getPrecision();
    }

    /**
     * {@inheritDoc}
     */
    public int getScale() {
        return this.type.getScale();
    }

    /**
     * @return the nullable
     */
    public boolean isNullable() {
        return this.nullable;
    }

    /**
     * @return the defaultValue
     */
    public String getDefaultValue() {
        return this.defaultValue;
    }

    /**
     * @return obtém a lista de valores permitidos
     */
    public SortedSet<AllowedValue> getAllowedValues() {
        return this.type.getAllowedValues();
    }
}
